import axios, { Axios, AxiosRequestConfig, AxiosResponse } from "axios";
import { EventEmitter } from "events";
import { PromiseUtil } from "../promise";

const event = new EventEmitter();

interface ICache {
  data: any;
  pending: boolean;
  expire?: number; // 过期时间
  status?: number;
  statusText?: string;
}

class Cache implements ICache {
  data = undefined;
  pending = false;
}

const cacheData: Record<string, ICache> = {};

/**
 * 构建 Axios 的缓存 key
 * @param config
 */
function getCacheKey(config: AxiosRequestConfig = {}): string {
  let reqParams = {};
  const { method, params, data } = config;
  const reqData = method === "get" ? params : data;
  if (typeof reqData === "string") {
    try {
      reqParams = JSON.parse(reqData);
    } catch (err) {
      console.error("parse cacheKey error:: ", err);
    }
  } else {
    reqParams = reqData;
  }
  const reqKey = {
    url: config.url,
    params: reqParams,
    method,
  };

  let key;
  try {
    key = btoa(JSON.stringify(reqKey));
  } catch (err) {
    console.error("btoa error::", err);
    key = JSON.stringify(reqKey);
  }

  return key;
}

/**
 * 获取带缓存的 Axios 组件
 *
 * 使用 AxiosUtil.newAxios
 */
export class AxiosUtil {
  /**
   * 对请求做处理
   * @param cfg
   */
  public static cacheRequest(cfg: AxiosRequestConfig): AxiosRequestConfig {
    if (cfg.headers) {
      const { cache, forceUpdate } = cfg.headers;
      if (cache) {
        const paramsKey = getCacheKey(cfg);

        if (!cacheData[paramsKey]) {
          cacheData[paramsKey] = new Cache();
        }

        const { data, pending, expire } = cacheData[paramsKey];
        if (pending) {
          cfg.headers.useCache = true.toString();
          cfg.adapter = () =>
            new Promise<AxiosResponse<any>>((resolve) => {
              event.once(paramsKey, (res: ICache) => {
                resolve({
                  data: res.data,
                  status: res.status!,
                  statusText: res.statusText!,
                  // @ts-ignore
                  headers: cfg.headers!,
                  config: {
                    ...cfg,
                  },
                  request: cfg,
                });
              });
            });
        } else if (!forceUpdate && data && expire && Date.now() < expire) {
          cfg.headers.useCache = true.toString();

          cfg.adapter = (config: AxiosRequestConfig) =>
            Promise.resolve<AxiosResponse<any>>({
              data,
              status: 200,
              statusText: "OK",
              // @ts-ignore
              headers: cfg.headers!,
              config: {
                ...cfg,
              },
              request: config,
            });
        } else {
          cacheData[paramsKey].pending = true;
        }
      }
    }
    // Do something before request is sent
    return cfg;
  }

  /**
   * 对返回值做处理
   * @param res 返回值
   */
  public static cacheResponse(res: AxiosResponse): AxiosResponse {
    const { config = {}, status, statusText } = res;
    const { cache, useCache } = config.headers!;

    if (cache && !useCache) {
      const paramsKey = getCacheKey(config);
      const resData = res.data;
      //  需缓存的接口，请求失败时不缓存结果
      cacheData[paramsKey] = {
        data: resData,
        pending: false,
        expire: Date.now() + Number(cache) * 1000,
        status,
        statusText,
      };
      if (status !== 200) {
        cacheData[paramsKey].expire = undefined;
      }
      event.emit(paramsKey, cacheData[paramsKey]);
    } else if (useCache) {
      console.log(`[AxiosTool]: ${config.url} 缓存!`);
    }
    return res;
  }

  public static newAxios(cfg: AxiosRequestConfig): Axios {
    const _axios = axios.create(cfg);

    // 追加缓存
    _axios.interceptors.request.use(AxiosUtil.cacheRequest, PromiseUtil.error);
    _axios.interceptors.response.use(AxiosUtil.cacheResponse, (error) => {
      if (error.response) {
        return AxiosUtil.cacheResponse(error.response);
      } else {
        return Promise.reject(error);
      }
    });

    return _axios;
  }
}
